#pragma once

#include "vec3.h"

template <class treal>
class basevec4
{
public:
    treal x;
    treal y;
    treal z;
    treal w;

    basevec4(void)
        :x(0), y(0), z(0), w(0)
    {
    }

    explicit basevec4(const treal * p)
        :x(p[0]), y(p[1]), z(p[2]), w(p[3])
    {
    }

    explicit basevec4(treal const& x0, treal const& y0, treal const& z0, treal const& w0 = 0)
        :x(x0), y(y0), z(z0), w(w0)
    {
    }

    explicit basevec4(basevec3<treal> const& v, treal const& w0 = 0)
        :x(v.x), y(v.y), z(v.z), w(w0)
    {
    }

    inline operator treal const*()const
    {
        return &x;
    }

    inline basevec4 & operator *= (treal scale)
    {
        x *= scale;
        y *= scale;
        z *= scale;
        w *= scale;
        return *this;
    }


    inline basevec4 & operator /= (treal const& scalar)
    {
        return *this *= (1/scalar);
    }

    inline basevec4 const operator / (treal const& scalar)const
    {
        return *this * (1/scalar);
    }

    inline basevec4 const operator-(basevec4 const& v)const
    {
        return basevec4(x - v.x, y - v.y, z - v.z, w - v.w);
    }

    inline basevec4 const operator-()const
    {
        return basevec4(-x, -y, -z, -w);
    }

    inline basevec4 const operator+()const
    {
        return *this;
    }

    inline basevec4 const operator*(basevec4 const& v)const
    {
        return basevec4(x * v.x, y * v.y, z * v.z, w * v.w);
    }

    inline basevec4 const operator/(basevec4 const& v)const
    {
        return basevec4(x / v.x, y / v.y, z / v.z, w / v.w);
    }

    inline basevec4 const operator+(basevec4 const& v)const
    {
        return basevec4(x + v.x, y + v.y, z + v.z, w + v.w);
    }

    inline basevec4 & operator+=(basevec4 const& v)
    {
        x += v.x;
        y += v.y;
        z += v.z;
        w += v.w;
        return *this;
    }

    inline basevec4 & operator*=(basevec4 const& v)
    {
        x *= v.x;
        y *= v.y;
        z *= v.z;
        w *= v.w;
        return *this;
    }

    inline basevec4 & operator/=(basevec4 const& v)
    {
        x /= v.x;
        y /= v.y;
        z /= v.z;
        w /= v.w;
        return *this;
    }

    inline basevec4 & operator-=(basevec4 const& v)
    {
        x -= v.x;
        y -= v.y;
        z -= v.z;
        w -= v.w;
        return *this;
    }

    inline basevec4 const operator*(treal scale)const
    {
        return basevec4(x * scale, y * scale, z * scale, w * scale);
    }

    inline treal getLength()const
    {
        return static_cast<treal>(sqrt(x * x + y * y + z * z + w * w));
    }

    inline vec3f tovec3() const
    {
        return vec3f(x, y, z);
    }

    inline vec3d tovec3d() const
    {
        return vec3d(x, y, z);
    }

    inline basevec3<treal> project()const
    {
        treal invW = 1 / w;
        return basevec3<treal>(x * invW, y * invW, z * invW);
    }

    inline void normalize()
    {
        treal invLength = 1 / getLength();
        x *= invLength;
        y *= invLength;
        z *= invLength;
        w *= invLength;
    }

    basevec4<double> tovec4d() const
    {
        return basevec4<double>(x, y, z, w);
    }

    basevec4<float> tovec4f() const
    {
        return basevec4<float>(x, y, z, w);
    }
};

template<class treal> inline
basevec4<treal> const operator*(treal const& scalar, basevec4<treal> const& v)
{
    return v * scalar;
}

template <class treal>
inline treal dot(basevec4<treal> const& a, basevec4<treal> const& b)
{
    return (a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w);
}

typedef basevec4<float> vec4f;
typedef basevec4<double> vec4d;
